package com.max.thread;

import com.max.file.FileMetadata;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.MessageFormat;

/**
 * Created by Maxwell Yu on 4/19/17.
 */
public class DownLoadThread extends Thread {
    private static final int DOWNLOAD_PER_SECOND = 1400;
    private static final int RANGE_STEP = 40;

    private FileMetadata metaData;
    private long startPosition;
    private long endPosition;
    private int reconnectTimes;
    private int PARTIAL_CONTENT_CODE = 206;

    private ResponseHandler<Boolean> responseHandler = response -> {
        FileChannel file = metaData.getResultFile();
        InputStream is = response.getEntity().getContent();

        file.position(startPosition);

        byte[] buffer = new byte[metaData.getBufferSize().intValue()];
        while (is.read(buffer) != -1) {

            ByteBuffer byteBuffer = ByteBuffer.allocate(metaData.getBufferSize().intValue());
            byteBuffer.put(buffer);
            file.write(byteBuffer);

        }
        is.close();
        return true;
    };

    public DownLoadThread(FileMetadata metadata, long startSize, long endPosition) {
        this.metaData = metadata;
        this.startPosition = startSize;
        this.endPosition = endPosition;
    }

    public void run() {

        try {
            metaData.getFreeThreads().acquire();

            printDoReconnectTimes();

            addSubThread();
            partialDownload();

            metaData.getFreeThreads().release();

        } catch (SocketTimeoutException timeoutException) {
            reconnectForTimeOut();
        } catch (FileNotFoundException e) {
            System.out.println("tFileNotFoundException");
            e.printStackTrace();
        } catch (ProtocolException e) {
            System.out.println("ProtocolException");
            e.printStackTrace();
        } catch (MalformedURLException e) {
            System.out.println("MalformedURLException");
            e.printStackTrace();
        } catch (IOException e) {

            reconnectForTimeOut();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void partialDownload() throws IOException, InterruptedException {
        System.out.println(" start " + startPosition + " - " + endPosition);

        HttpGet httpGet = new HttpGet(metaData.getFileUrl());
        while (startPosition < endPosition) {
            httpGet.setHeader("Range", "bytes=" + startPosition + 1 + "-" + startPosition + metaData.getBufferSize());

            FileChannel resultFile = metaData.getResultFile();
            resultFile.position(startPosition);

            metaData.getHttpClient().execute(httpGet, responseHandler);
            startPosition = startPosition + metaData.getBufferSize();

            addSubThread();
        }
        System.out.println("finish download " + startPosition + " - " + endPosition);
    }

    private void addSubThread() throws IOException, InterruptedException {
        if (metaData.getFreeThreads().tryAcquire() && (startPosition + DOWNLOAD_PER_SECOND <= endPosition)) {
            long middlePosition = (startPosition + endPosition) / 2;

            metaData.getPool().submit(new DownLoadThread(metaData, startPosition, middlePosition));

            this.startPosition = middlePosition + 1;
        }
    }

    private void reconnectForTimeOut() {
        reconnectTimes++;
        try {
            Thread.sleep(5000);
            partialDownload();
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        }
    }


    private void printDoReconnectTimes() {
        if (reconnectTimes != 0) {
            System.out.println(
                    MessageFormat.format("reconnect for blocks {0} - {1} {2} times", startPosition, endPosition,
                            reconnectTimes));
        }
    }
}
